#include "ioqueue.h"
#include "interrupt.h"
#include "global.h"
#include "debug.h"

/* 初始化io队列ioq */
void ioqueue_init(struct ioqueue *ioq) {
    lock_init(&ioq->lock);
    ioq->producer = ioq->consumer = NULL;
    ioq->head = ioq->tail = 0;
}

/* 返回pos在缓冲区的下一个位置值 */
static int next_pos(int32_t pos) {
    return (pos + 1) % bufsize;
}

/* 判断队列是否已满 */
bool ioq_full(struct ioqueue *ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    return (next_pos(ioq->head) == ioq->tail);
}

/* 判断队列是否已空 */
bool ioq_empty(struct ioqueue *ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    return (ioq->head == ioq->tail);
}

/* 使当前生产者或消费者在此线程上等待,传入参数为ioq->producer该指针所在的地址&ioq->producer/consumer  */
static void ioq_wait(struct task_struct **waiter) {
    ASSERT(*waiter == NULL && waiter != NULL); // 无其它线程在此缓冲区上睡眠 && 记录睡眠者的指针的地址存在
    *waiter = running_thread();                 // 将调用者自己的pcb指针写入等待者ioq->producer/consumer = running_thread()
    thread_block(TASK_BLOCKED);                 // 调用者自己将自己挂起
}

/* 唤醒waiter */
static void wakeup(struct task_struct **waiter) {
    ASSERT(*waiter != NULL);
    thread_unblock(*waiter);
    *waiter = NULL;
}

/* 消费者从ioq队列中获取一个字符 */
char ioq_getchar(struct ioqueue *ioq) {
    ASSERT(intr_get_status() == INTR_OFF);

    while (ioq_empty(ioq)) {
        // 若缓冲区空,则睡眠,等待写入者唤醒它
        lock_acquire(&ioq->lock);   // 申请缓冲区锁,保证缓冲区操作互斥
        ioq_wait(&ioq->consumer);   // 持有锁后,将自己睡眠,并装入消费者等待者中,等待一个生产者唤醒它
        lock_release(&ioq->lock);   // 释放缓存区锁,因为是while,再判断一次是否empty(存在竞争关系)
    }

    char byte = ioq->buf[ioq->tail];
    ioq->tail = next_pos(ioq->tail);

    if (ioq->producer != NULL) {
        // 有在睡眠的生产者
        wakeup(&ioq->producer);
    }

    return byte;
}

/* 生产者往ioq队列中写入一个字符byte */
void ioq_putchar(struct ioqueue *ioq, char byte) {
    ASSERT(intr_get_status() == INTR_OFF);

    while (ioq_full(ioq)) {
        // 若缓冲区满,则睡眠,等待消费者唤醒它
        lock_acquire(&ioq->lock);
        ioq_wait(&ioq->consumer);   // 持有锁后,将自己睡眠,等待一个消费者唤醒它,持有锁可以防止在睡眠让出cpu后,其他也访问此队列的生产者进入ioq_wait这一步,其他生产者会在acquire锁down信号量的时候阻塞住
        lock_release(&ioq->lock);
    }

    ioq->buf[ioq->head] = byte;
    ioq->head = next_pos(ioq->head);

    if (ioq->consumer != NULL) {
        wakeup(&ioq->consumer);
    }
}